<?php

/**
 * WebBrowser plugin.
 *
 */
class phpQueryObjectPlugin_WebBrowser
{
    /**
     * Limit binded methods to specified ones.
     *
     * @var array
     */
    public static $phpQueryMethods = null;

    /**
     * Enter description here...
     *
     * @param phpQueryObject $self
     * @todo support 'reset' event
     */
    public static function WebBrowser($self, $callback = null, $location = null)
    {
        $self = $self->_clone()->toRoot();
        $location = $location
            ? $location
            // TODO use document.location
            : $self->document->xhr->getUri(true);
        // FIXME tmp
        $self->document->WebBrowserCallback = $callback;
        if (!$location)
            throw new Exception('Location needed to activate WebBrowser plugin !');
        else {
            $self->bind('click', array($location, $callback), array('phpQueryPlugin_WebBrowser', 'hadleClick'));
            $self->bind('submit', array($location, $callback), array('phpQueryPlugin_WebBrowser', 'handleSubmit'));
        }
    }

    public static function browser($self, $callback = null, $location = null)
    {
        return $self->WebBrowser($callback, $location);
    }

    public static function downloadTo($self, $dir = null, $filename = null)
    {
        $url = null;
        if ($self->is('a[href]'))
            $url = $self->attr('href');
        else if ($self->find('a')->length)
            $url = $self->find('a')->attr('href');
        if ($url) {
            $url = resolve_url($self->document->location, $url);
            if (!$dir)
                $dir = getcwd();
            // TODO resolv name from response headers
            if (!$filename) {
                $matches = null;
                preg_match('@/([^/]+)$@', $url, $matches);
                $filename = $matches[1];
            }
            //print $url;
            $path = rtrim($dir, '/') . '/' . $filename;
            phpQuery::debug("Requesting download of $url\n");
            // TODO use AJAX instead of file_get_contents
            file_put_contents($path, file_get_contents($url));
        }
        return $self;
    }

    /**
     * Method changing browser location.
     * Fires callback registered with WebBrowser(), if any.
     * @param $self
     * @param $url
     * @return unknown_type
     */
    public static function location($self, $url = null)
    {
        // TODO if ! $url return actual location ???
        $xhr = isset($self->document->xhr)
            ? $self->document->xhr
            : null;
        $xhr = phpQuery::ajax(array(
            'url' => $url,
        ), $xhr);
        $return = false;
        if ($xhr->getLastResponse()->isSuccessful()) {
            $return = phpQueryPlugin_WebBrowser::browserReceive($xhr);
            if (isset($self->document->WebBrowserCallback))
                phpQuery::callbackRun(
                    $self->document->WebBrowserCallback,
                    array($return)
                );
        }
        return $return;
    }
}

class phpQueryPlugin_WebBrowser
{
    /**
     *
     * @param $url
     * @param $callback
     * @param $param1
     * @param $param2
     * @param $param3
     * @return Zend_Http_Client
     */
    public static function browserGet($url, $callback,
                                      $param1 = null, $param2 = null, $param3 = null)
    {
        phpQuery::debug("[WebBrowser] GET: $url");
        self::authorizeHost($url);
        $xhr = phpQuery::ajax(array(
            'type' => 'GET',
            'url' => $url,
            'dataType' => 'html',
        ));
        $paramStructure = null;
        if (func_num_args() > 2) {
            $paramStructure = func_get_args();
            $paramStructure = array_slice($paramStructure, 2);
        }
        if ($xhr->getLastResponse()->isSuccessful()) {
            phpQuery::callbackRun($callback,
                array(self::browserReceive($xhr)->WebBrowser()),
                $paramStructure
            );
//			phpQuery::callbackRun($callback, array(
//				self::browserReceive($xhr)//->WebBrowser($callback)
//			));
            return $xhr;
        } else {
            throw new Exception("[WebBrowser] GET request failed; url: $url");
            return false;
        }
    }

    /**
     *
     * @param $url
     * @param $data
     * @param $callback
     * @param $param1
     * @param $param2
     * @param $param3
     * @return Zend_Http_Client
     */
    public static function browserPost($url, $data, $callback,
                                       $param1 = null, $param2 = null, $param3 = null)
    {
        self::authorizeHost($url);
        $xhr = phpQuery::ajax(array(
            'type' => 'POST',
            'url' => $url,
            'dataType' => 'html',
            'data' => $data,
        ));
        $paramStructure = null;
        if (func_num_args() > 3) {
            $paramStructure = func_get_args();
            $paramStructure = array_slice($paramStructure, 3);
        }
        if ($xhr->getLastResponse()->isSuccessful()) {
            phpQuery::callbackRun($callback,
                array(self::browserReceive($xhr)->WebBrowser()),
                $paramStructure
            );
//			phpQuery::callbackRun($callback, array(
//				self::browserReceive($xhr)//->WebBrowser($callback)
//			));
            return $xhr;
        } else
            return false;
    }

    /**
     *
     * @param $ajaxSettings
     * @param $callback
     * @param $param1
     * @param $param2
     * @param $param3
     * @return Zend_Http_Client
     */
    public static function browser($ajaxSettings, $callback,
                                   $param1 = null, $param2 = null, $param3 = null)
    {
        self::authorizeHost($ajaxSettings['url']);
        $xhr = phpQuery::ajax(
            self::ajaxSettingsPrepare($ajaxSettings)
        );
        $paramStructure = null;
        if (func_num_args() > 2) {
            $paramStructure = func_get_args();
            $paramStructure = array_slice($paramStructure, 2);
        }
        if ($xhr->getLastResponse()->isSuccessful()) {
            phpQuery::callbackRun($callback,
                array(self::browserReceive($xhr)->WebBrowser()),
                $paramStructure
            );
//			phpQuery::callbackRun($callback, array(
//				self::browserReceive($xhr)//->WebBrowser($callback)
//			));
            return $xhr;
        } else
            return false;
    }

    protected static function authorizeHost($url)
    {
        $host = parse_url($url, PHP_URL_HOST);
        if ($host)
            phpQuery::ajaxAllowHost($host);
    }

    protected static function ajaxSettingsPrepare($settings)
    {
        unset($settings['success']);
        unset($settings['error']);
        return $settings;
    }

    /**
     * @param Zend_Http_Client $xhr
     */
    public static function browserReceive($xhr)
    {
        phpQuery::debug("[WebBrowser] Received from " . $xhr->getUri(true));
        // TODO handle meta redirects
        $body = $xhr->getLastResponse()->getBody();

        // XXX error ???
        if (strpos($body, '<!doctype html>') !== false) {
            $body = '<html>'
                . str_replace('<!doctype html>', '', $body)
                . '</html>';
        }
        $pq = phpQuery::newDocument($body);
        $pq->document->xhr = $xhr;
        $pq->document->location = $xhr->getUri(true);
        $refresh = $pq->find('meta[http-equiv=refresh]')
            ->add('meta[http-equiv=Refresh]');
        if ($refresh->size()) {
//			print htmlspecialchars(var_export($xhr->getCookieJar()->getAllCookies(), true));
//			print htmlspecialchars(var_export($xhr->getLastResponse()->getHeader('Set-Cookie'), true));
            phpQuery::debug("Meta redirect... '{$refresh->attr('content')}'\n");
            // there is a refresh, so get the new url
            $content = $refresh->attr('content');
            $urlRefresh = substr($content, strpos($content, '=') + 1);
            $urlRefresh = trim($urlRefresh, '\'"');
            // XXX not secure ?!
            phpQuery::ajaxAllowURL($urlRefresh);
//			$urlRefresh = urldecode($urlRefresh);
            // make ajax call, passing last $xhr object to preserve important stuff
            $xhr = phpQuery::ajax(array(
                'type' => 'GET',
                'url' => $urlRefresh,
                'dataType' => 'html',
            ), $xhr);
            if ($xhr->getLastResponse()->isSuccessful()) {
                // if all is ok, repeat this method...
                return call_user_func_array(
                    array('phpQueryPlugin_WebBrowser', 'browserReceive'), array($xhr)
                );
            }
        } else
            return $pq;
    }

    /**
     *
     * @param $e
     * @param $callback
     * @return unknown_type
     */
    public static function hadleClick($e, $callback = null)
    {
        $node = phpQuery::pq($e->target);
        $type = null;
        if ($node->is('a[href]')) {
            // TODO document.location
            $xhr = isset($node->document->xhr)
                ? $node->document->xhr
                : null;
            $xhr = phpQuery::ajax(array(
                'url' => resolve_url($e->data[0], $node->attr('href')),
                'referer' => $node->document->location,
            ), $xhr);
            if ((!$callback || !($callback instanceof Callback)) && $e->data[1])
                $callback = $e->data[1];
            if ($xhr->getLastResponse()->isSuccessful() && $callback)
                phpQuery::callbackRun($callback, array(
                    self::browserReceive($xhr)
                ));
        } else if ($node->is(':submit') && $node->parents('form')->size())
            $node->parents('form')->trigger('submit', array($e));
    }

    /**
     * Enter description here...
     *
     * @param unknown_type $e
     * @TODO trigger submit for form after form's  submit button has a click event
     */
    public static function handleSubmit($e, $callback = null)
    {
        $node = phpQuery::pq($e->target);
        if (!$node->is('form') || !$node->is('[action]'))
            return;
        // TODO document.location
        $xhr = isset($node->document->xhr)
            ? $node->document->xhr
            : null;
        $submit = pq($e->relatedTarget)->is(':submit')
            ? $e->relatedTarget
            // will this work ?
//			: $node->find(':submit:first')->get(0);
            : $node->find('*:submit:first')->get(0);
        $data = array();
        foreach ($node->serializeArray($submit) as $r)
            // XXXt.c maybe $node->not(':submit')->add($sumit) would be better ?
//		foreach($node->serializeArray($submit) as $r)
            $data[$r['name']] = $r['value'];
        $options = array(
            'type' => $node->attr('method')
                ? $node->attr('method')
                : 'GET',
            'url' => resolve_url($e->data[0], $node->attr('action')),
            'data' => $data,
            'referer' => $node->document->location,
//			'success' => $e->data[1],
        );
        if ($node->attr('enctype'))
            $options['contentType'] = $node->attr('enctype');
        $xhr = phpQuery::ajax($options, $xhr);
        if ((!$callback || !($callback instanceof Callback)) && $e->data[1])
            $callback = $e->data[1];
        if ($xhr->getLastResponse()->isSuccessful() && $callback)
            phpQuery::callbackRun($callback, array(
                self::browserReceive($xhr)
            ));
    }
}

/**
 *
 * @param unknown_type $parsed
 * @return unknown
 * @link http://www.php.net/manual/en/function.parse-url.php
 * @author stevenlewis at hotmail dot com
 */
function glue_url($parsed)
{
    if (!is_array($parsed)) return false;
    $uri = isset($parsed['scheme']) ? $parsed['scheme'] . ':' . ((strtolower($parsed['scheme']) == 'mailto') ? '' : '//') : '';
    $uri .= isset($parsed['user']) ? $parsed['user'] . ($parsed['pass'] ? ':' . $parsed['pass'] : '') . '@' : '';
    $uri .= isset($parsed['host']) ? $parsed['host'] : '';
    $uri .= isset($parsed['port']) ? ':' . $parsed['port'] : '';
    if (isset($parsed['path'])) {
        $uri .= (substr($parsed['path'], 0, 1) == '/') ? $parsed['path'] : '/' . $parsed['path'];
    }
    $uri .= isset($parsed['query']) ? '?' . $parsed['query'] : '';
    $uri .= isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
    return $uri;
}

/**
 * Enter description here...
 *
 * @param unknown_type $base
 * @param unknown_type $url
 * @return unknown
 * @author adrian-php at sixfingeredman dot net
 */
function resolve_url($base, $url)
{
    if (!strlen($base)) return $url;
    // Step 2
    if (!strlen($url)) return $base;
    // Step 3
    if (preg_match('!^[a-z]+:!i', $url)) return $url;
    $base = parse_url($base);
    if ($url{0} == "#") {
        // Step 2 (fragment)
        $base['fragment'] = substr($url, 1);
        return unparse_url($base);
    }
    unset($base['fragment']);
    unset($base['query']);
    if (substr($url, 0, 2) == "//") {
        // Step 4
        return unparse_url(array(
            'scheme' => $base['scheme'],
            'path' => substr($url, 2),
        ));
    } else if ($url{0} == "/") {
        // Step 5
        $base['path'] = $url;
    } else {
        // Step 6
        $path = explode('/', $base['path']);
        $url_path = explode('/', $url);
        // Step 6a: drop file from base
        array_pop($path);
        // Step 6b, 6c, 6e: append url while removing "." and ".." from
        // the directory portion
        $end = array_pop($url_path);
        foreach ($url_path as $segment) {
            if ($segment == '.') {
                // skip
            } else if ($segment == '..' && $path && $path[sizeof($path) - 1] != '..') {
                array_pop($path);
            } else {
                $path[] = $segment;
            }
        }
        // Step 6d, 6f: remove "." and ".." from file portion
        if ($end == '.') {
            $path[] = '';
        } else if ($end == '..' && $path && $path[sizeof($path) - 1] != '..') {
            $path[sizeof($path) - 1] = '';
        } else {
            $path[] = $end;
        }
        // Step 6h
        $base['path'] = join('/', $path);

    }
    // Step 7
    return glue_url($base);
}
